<?php
/**
 * @see Alinks_Form_Element_Button
 */
require_once('Alinks/Form/Element/Button.php');

/**
 * @see Alinks_Form_Element_Checkbox
 */
require_once('Alinks/Form/Element/Checkbox.php');

/**
 * @see Alinks_Form_Element_Hidden
 */
require_once('Alinks/Form/Element/Hidden.php');

/**
 * @see Alinks_Form_Element_Password
 */
require_once('Alinks/Form/Element/Password.php');

/**
 * @see Alinks_Form_Element_Radio
 */
require_once('Alinks/Form/Element/Radio.php');

/**
 * @see Alinks_Form_Element_RadioGroup
 */
require_once('Alinks/Form/Element/RadioGroup.php');

/**
 * @see Alinks_Form_Element_Select
 */
require_once('Alinks/Form/Element/Select.php');

/**
 * @see Alinks_Form_Element_Text
 */
require_once('Alinks/Form/Element/Text.php');

/**
 * @see Alinks_Form_Element_Textarea
 */
require_once('Alinks/Form/Element/Textarea.php');

/**
 * Represents a form to be displayed in the admin section
 *
 * Copyright (C) 2008  Sean Hickey
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @author Sean Hickey <sean@headzoo.com>
 * @version $Id: Form.php 379 2008-07-03 05:25:29Z sean $
 * @package Alinks_Form
 */
class Alinks_Form
{
	/**
	 * Class that is applied to the wrapping li element when a
	 * form field is invalid
	 */
	const CLASS_INVALID_ELEMENT = 'alinks_invalid';
	
	/**
	 * Class applied to the wrapping ul element when displaying
	 * invalid messages
	 */
	const CLASS_INVALID_MESSAGES = 'alinks_invalid_messages';
	
	/**
	 * Class for the ul element that each form is wrapped in
	 */
	const CLASS_FORM = 'alinks_form';
	
	/**
	 * Class for fieldsets where factory forms are shown
	 */
	const CLASS_FIELDSET_FACTORIES = 'alinks_factory_settings';
	
	/**
	 * Hidden form element name indicating that a form is coming
	 * from aLinks
	 */
	const FORM_ALINKS = '_alinks_form';
	
	/**
	 * Hidden form element name indicating the factory the form
	 * is for
	 */
	const FORM_FACTORY = '_alinks_factory';
	
	/**
	 * Action fvor nonce fields
	 */
	const NONCE_ACTION = 'alinks';
	
	/**
	 * The submit method
	 *
	 * @var string
	 */
	protected $_method = 'POST';
	
	/**
	 * The submit action
	 *
	 * @var string
	 */
	protected $_action = '';
	
	/**
	 * The name of the form
	 *
	 * @var string
	 */
	protected $_name = 'alinks';
	
	/**
	 * The factory that the form belongs to
	 *
	 * @var Alinks_Factory_Interface
	 */
	protected $_factory = null;
	
	/**
	 * The elements in the form
	 *
	 * @var array
	 */
	protected $_elements = array();
	
	/**
	 * Constructor
	 *
	 * @param Alinks_Factory_Interface $factory The that's creating this form
	 */
	public function __construct(Alinks_Factory_Interface $factory)
	{
		$this->setFactory($factory);
	}
	
	/**
	 * Sets the submit method
	 *
	 * @param string $method The method
	 * @return Alinks_Form
	 */
	public function setMethod($method)
	{
		$this->_method = $method;
		return $this;
	}
	
	/**
	 * Returns the submit method
	 *
	 * @return string
	 */
	public function getMethod()
	{
		return $this->_method;
	}
	
	/**
	 * Sets the submit action
	 *
	 * @param string $action The action
	 * @return Alinks_Form
	 */
	public function setAction($action)
	{
		$this->_action = $action;
		return $this;
	}
	
	/**
	 * Returns the submit action
	 *
	 * @return string
	 */
	public function getAction()
	{
		return $this->_action;
	}
	
	/**
	 * Sets the form name
	 *
	 * @param string $name The form name
	 * @return Alinks_Form
	 */
	public function setName($name)
	{
		$this->_name = $name;
		return $this;
	}
	
	/**
	 * Returns the name of the form
	 *
	 * @return string
	 */
	public function getName()
	{
		return $this->_name;
	}
	
	/**
	 * Sets the factory that the form belongs to
	 *
	 * @param Alinks_Factory_Interface $factory The factory
	 * @return Alinks_Form
	 */
	public function setFactory(Alinks_Factory_Interface $factory)
	{
		$this->_factory = $factory;
		return $this;
	}
	
	/**
	 * Returns the factory the form belongs to
	 *
	 * @return Alinks_Factory_Interface
	 */
	public function getFactory()
	{
		return $this->_factory;
	}
	
	/**
	 * Adds an input element to the form
	 *
	 * @param Alinks_Form_Element_Interface $element The element
	 * @return Alinks_Form
	 */
	public function addElement(Alinks_Form_Element_Interface $element)
	{
		$this->_elements[$element->getName()] = $element;
		return $this;
	}
	
	/**
	 * Returns the input elements
	 *
	 * If $name is specified, then the element with that name will be
	 * returned.
	 *
	 * @return Alinks_Form_Element_Abstract|Alinks_Form_Element_Abstract[]
	 * @throws Alinks_Form_Exception
	 */
	public function getElements($name = null)
	{
		if (null !== $name) {
			if (!isset($this->_elements[$name])) {
				/**
				 * @see Alinks_Form_Exception
				 */
				require_once('Alinks/Form/Exception.php');
				throw new Alinks_Form_Exception("No element exists with the name $name");
			}
			return $this->_elements[$name];
		}
		
		return $this->_elements;
	}
	
	/**
	 * Sets the values for the elements in the form
	 *
	 * Sets the values for all the form elemnts added to the form. It's
	 * expected that $values will be submitted form data.
	 *
	 * @param array $values The input values
	 * @return Alinks_Form
	 */
	public function setValues(array $values)
	{
		foreach($values as $key => $value) {
			if (isset($this->_elements[$key])) {
				$this->_elements[$key]->setValue($value);
			}
		}
		
		return $this;
	}
	
	/**
	 * Gets the values from the input elements
	 *
	 * Returns all the set values for each form element. The returned array
	 * will be indexed with the element's name.
	 *
	 * @return array
	 */
	public function getValues()
	{
		$values = array();
		foreach($this->_elements as $key => $element) {
			$values[$key] = $element->getValue();
		}
		
		return $values;
	}
	
	/**
	 * Returns if any of the form elements have been set with an invalid value
	 *
	 * If $name is specified, then the method returns if that element
	 * is invalid.
	 *
	 * @param string $name The element name
	 * @return bool
	 */
	public function hasInvalid($name = null)
	{
		if (null !== $name) {
			if (!isset($this->_elements[$name])) {
				/**
				 * @see Alinks_Form_Exception
				 */
				require_once('Alinks/Form/Exception.php');
				throw new Alinks_Form_Exception("No element exists with the name $name");
			}
			
			return !$this->_elements[$name]->isValid();
		}
		
		$invalid = false;
		foreach($this->_elements as $element) {
			if (!$element->isValid()) {
				$invalid = true;
				break;
			}
		}
		
		return $invalid;
	}
	
	/**
	 * Returns the invalid elements
	 *
	 * Returns the names of the form elements that are invalid.
	 *
	 * @return array
	 */
	public function getInvalids()
	{
		$invalids = array();
		foreach($this->_elements as $name => $element) {
			if (!$element->isValid()) {
				$invalids[] = $name;
			}
		}
		
		return $invalids;
	}
	
	/**
	 * Returns the HTML for the form
	 *
	 * @return string
	 */
	public function getHtml()
	{
		$html = null;
		$html .= sprintf('<form method="%s" action="%s" name="%s">%s',
			$this->getMethod(), $this->getAction(), $this->getName(), "\r\n");
		$html .= '<input type="hidden" name="' . self::FORM_ALINKS . '" value="1" />' . "\r\n";
		$html .= '<input type="hidden" name="' . self::FORM_FACTORY . '" value="' . $this->_factory->getSafeName() . '" />' . "\r\n";
		$html .= '<input type="hidden" name="_wpnonce" value="' . wp_create_nonce(self::NONCE_ACTION) . '" />' . "\r\n";
		$html .= '<input type="hidden" name="_wp_http_referer" value="'. attribute_escape($_SERVER['REQUEST_URI']) . '" />' . "\r\n";
		
		$html .= '<ul class="' . self::CLASS_FORM . '">';
		$alt_counter = 0;
		
		foreach($this->_elements as $element) {
			/**
			 * Prevent ID clashes by prefixing all element IDs with
			 * the name of the factory.
			 */
			$element->setId($this->_factory->getSafeName() . '_' . $element->getId());
			
			$class = '';
			if ($alt_counter % 2) {
				$class = 'alt';
			}
			if (!$element->isValid()) {
				$class .= ' ' . self::CLASS_INVALID_ELEMENT;
			}
			
			if (!($element instanceof Alinks_Form_Element_Hidden)) {
				$html .= "<li class='$class'>\r\n";
			}
			
			/**
			 * Radio buttons already have the label built into
			 * the HTML output. Don't add a label now. This is a hack that
			 * needs to be fixed.
			 */
			if (!$element instanceof Alinks_Form_Element_Radio && !$element instanceof Alinks_Form_Element_RadioGroup) {
				$html .= $element->getLabel();
			}
			
			$html .= $element->getHtml();
			$html .= '<div><small>' . $element->getDescription() . '</small></div>';
			$html .= "\r\n";
			
			if (!$element->isValid()) {
				$html .= '<ul class="' . self::CLASS_INVALID_MESSAGES . '">';
				foreach($element->getMessages() as $message) {
					$html .= '<li>' . htmlspecialchars($message) . '</li>';
				}
				$html .= '</ul>';
			}
			if (!($element instanceof Alinks_Form_Element_Hidden)) {
				$html .= "</li>\r\n";
			}
			++$alt_counter;
		}
		$html .= "</ul>\r\n";
		$html .= '<div class="submit"><input type="submit" value="Update &raquo;" /></div>';
		$html .= "</form>\r\n";
		
		return $html;
	}
	
	/**
	 * Returns the HTML for the form
	 *
	 * @return string
	 */
	public function __toString()
	{
		return $this->getHtml();
	}
}